/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.threadsample; import com.example.android.threadsample.PhotoDecodeRunnable.TaskRunnableDecodeMethods; import com.example.android.threadsample.PhotoDownloadRunnable.TaskRunnableDownloadMethods; import android.graphics.Bitmap; import java.lang.ref.WeakReference; import java.net.URL; /** * This class manages PhotoDownloadRunnable and PhotoDownloadRunnable objects. It does't perform * the download or decode; instead, it manages persistent storage for the tasks that do the work. * It does this by implementing the interfaces that the download and decode classes define, and * then passing itself as an argument to the constructor of a download or decode object. In effect, * this allows PhotoTask to start on a Thread, run a download in a delegate object, then * run a decode, and then start over again. This class can be pooled and reused as necessary. */ public class PhotoTask implements TaskRunnableDownloadMethods, TaskRunnableDecodeMethods { /* * Creates a weak reference to the ImageView that this Task will populate. * The weak reference prevents memory leaks and crashes, because it * automatically tracks the "state" of the variable it backs. If the * reference becomes invalid, the weak reference is garbage- collected. This * technique is important for referring to objects that are part of a * component lifecycle. Using a hard reference may cause memory leaks as the * value continues to change; even worse, it can cause crashes if the * underlying component is destroyed. Using a weak reference to a View * ensures that the reference is more transitory in nature. */ private WeakReference<PhotoView> mImageWeakRef; // The image's URL private URL mImageURL; // The width and height of the decoded image private int mTargetHeight; private int mTargetWidth; // Is the cache enabled for this transaction? private boolean mCacheEnabled; /* * Field containing the Thread this task is running on. */ Thread mThreadThis; /* * Fields containing references to the two runnable objects that handle downloading and * decoding of the image. */ private Runnable mDownloadRunnable; private Runnable mDecodeRunnable; // A buffer for containing the bytes that make up the image byte[] mImageBuffer; // The decoded image private Bitmap mDecodedImage; // The Thread on which this task is currently running. private Thread mCurrentThread; /* * An object that contains the ThreadPool singleton. */ private static PhotoManager sPhotoManager; /** * Creates an PhotoTask containing a download object and a decoder object. */ PhotoTask() { // Create the runnables mDownloadRunnable = new PhotoDownloadRunnable(this); mDecodeRunnable = new PhotoDecodeRunnable(this); sPhotoManager = PhotoManager.getInstance(); } /** * Initializes the Task * * @param photoManager A ThreadPool object * @param photoView An ImageView instance that shows the downloaded image * @param cacheFlag Whether caching is enabled */ void initializeDownloaderTask( PhotoManager photoManager, PhotoView photoView, boolean cacheFlag) { // Sets this object's ThreadPool field to be the input argument sPhotoManager = photoManager; // Gets the URL for the View mImageURL = photoView.getLocation(); // Instantiates the weak reference to the incoming view mImageWeakRef = new WeakReference<PhotoView>(photoView); // Sets the cache flag to the input argument mCacheEnabled = cacheFlag; // Gets the width and height of the provided ImageView mTargetWidth = photoView.getWidth(); mTargetHeight = photoView.getHeight(); } // Implements HTTPDownloaderRunnable.getByteBuffer @Override public byte[] getByteBuffer() { // Returns the global field return mImageBuffer; } /** * Recycles an PhotoTask object before it's put back into the pool. One reason to do * this is to avoid memory leaks. */ void recycle() { // Deletes the weak reference to the imageView if ( null != mImageWeakRef ) { mImageWeakRef.clear(); mImageWeakRef = null; } // Releases references to the byte buffer and the BitMap mImageBuffer = null; mDecodedImage = null; } // Implements PhotoDownloadRunnable.getTargetWidth. Returns the global target width. @Override public int getTargetWidth() { return mTargetWidth; } // Implements PhotoDownloadRunnable.getTargetHeight. Returns the global target height. @Override public int getTargetHeight() { return mTargetHeight; } // Detects the state of caching boolean isCacheEnabled() { return mCacheEnabled; } // Implements PhotoDownloadRunnable.getImageURL. Returns the global Image URL. @Override public URL getImageURL() { return mImageURL; } // Implements PhotoDownloadRunnable.setByteBuffer. Sets the image buffer to a buffer object. @Override public void setByteBuffer(byte[] imageBuffer) { mImageBuffer = imageBuffer; } // Delegates handling the current state of the task to the PhotoManager object void handleState(int state) { sPhotoManager.handleState(this, state); } // Returns the image that PhotoDecodeRunnable decoded. Bitmap getImage() { return mDecodedImage; } // Returns the instance that downloaded the image Runnable getHTTPDownloadRunnable() { return mDownloadRunnable; } // Returns the instance that decode the image Runnable getPhotoDecodeRunnable() { return mDecodeRunnable; } // Returns the ImageView that's being constructed. public PhotoView getPhotoView() { if ( null != mImageWeakRef ) { return mImageWeakRef.get(); } return null; } /* * Returns the Thread that this Task is running on. The method must first get a lock on a * static field, in this case the ThreadPool singleton. The lock is needed because the * Thread object reference is stored in the Thread object itself, and that object can be * changed by processes outside of this app. */ public Thread getCurrentThread() { synchronized(sPhotoManager) { return mCurrentThread; } } /* * Sets the identifier for the current Thread. This must be a synchronized operation; see the * notes for getCurrentThread() */ public void setCurrentThread(Thread thread) { synchronized(sPhotoManager) { mCurrentThread = thread; } } // Implements ImageCoderRunnable.setImage(). Sets the Bitmap for the current image. @Override public void setImage(Bitmap decodedImage) { mDecodedImage = decodedImage; } // Implements PhotoDownloadRunnable.setHTTPDownloadThread(). Calls setCurrentThread(). @Override public void setDownloadThread(Thread currentThread) { setCurrentThread(currentThread); } /* * Implements PhotoDownloadRunnable.handleHTTPState(). Passes the download state to the * ThreadPool object. */ @Override public void handleDownloadState(int state) { int outState; // Converts the download state to the overall state switch(state) { case PhotoDownloadRunnable.HTTP_STATE_COMPLETED: outState = PhotoManager.DOWNLOAD_COMPLETE; break; case PhotoDownloadRunnable.HTTP_STATE_FAILED: outState = PhotoManager.DOWNLOAD_FAILED; break; default: outState = PhotoManager.DOWNLOAD_STARTED; break; } // Passes the state to the ThreadPool object. handleState(outState); } // Implements PhotoDecodeRunnable.setImageDecodeThread(). Calls setCurrentThread(). @Override public void setImageDecodeThread(Thread currentThread) { setCurrentThread(currentThread); } /* * Implements PhotoDecodeRunnable.handleDecodeState(). Passes the decoding state to the * ThreadPool object. */ @Override public void handleDecodeState(int state) { int outState; // Converts the decode state to the overall state. switch(state) { case PhotoDecodeRunnable.DECODE_STATE_COMPLETED: outState = PhotoManager.TASK_COMPLETE; break; case PhotoDecodeRunnable.DECODE_STATE_FAILED: outState = PhotoManager.DOWNLOAD_FAILED; break; default: outState = PhotoManager.DECODE_STARTED; break; } // Passes the state to the ThreadPool object. handleState(outState); } }